package openglsuperbible.Chapter7;

import de.matthiasmann.twl.utils.PNGDecoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import openglsuperbible.glutils.*;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL30.*;

/**
 *
 * @author Matthew
 */
public class MultiTexture {
        public static final int DISPLAY_HEIGHT = 600;
        public static final int DISPLAY_WIDTH = 800;

        public long lastFrame;
       
        private Shader reflectionShader;
        private Shader skyBoxShader;
        private MatrixStack modelViewMatrix;  // Modelview Matrix
        private MatrixStack projectionMatrix; // Projection Matrix
        private GLFrustrum frustrum;    // View Frustum
        private GeometryTransform transformPipeline; // Geometry Transform Pipeline
        private GLFrame cameraFrame;
        private GLBatch sphere;
        private GLBatch cube;
        private int cubemap;
        private int tarnish;
        private float angle = 0.0f;
        private String[] cubeNames = { "pos_x", "neg_x", "pos_y", "neg_y", "pos_z", "neg_z" };
        
        
        public void initGL()
        {
            // Black background
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            reflectionShader = new Shader("ReflectionMulti","ReflectionMulti"); 
            skyBoxShader = new Shader("SkyBox","SkyBox"); 
             
            glEnable(GL_DEPTH_TEST);
            glCullFace(GL_BACK);
            glFrontFace(GL_CCW);

            
            frustrum = new GLFrustrum();               
            modelViewMatrix = new MatrixStack();
            cameraFrame = new GLFrame();
            sphere = GLBatchFactory.makeSphere(1.0f, 32, 24); 
            cube = GLBatchFactory.makeCube(1000.0f);
            
            cubemap = loadCubeMap(cubeNames) ;
            tarnish =  loadTexture("tarnish",GL_LINEAR_MIPMAP_LINEAR,GL_LINEAR,GL_CLAMP_TO_EDGE);
            
        // Set textures to their texture units
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, tarnish);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
        }
        
              
       

    void render()
    {    
        angle += 0.005f * getDelta();	
	// Clear the window with current clearing color
	 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

         float[] mCamera = cameraFrame.getCameraMatrix();
         float[] mCameraRotation = cameraFrame.getCameraOrientationf();         
         float[] mCameraInverse = Math3D.invertMatrix44(mCameraRotation);
         glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap);
         modelViewMatrix.push();
            modelViewMatrix.multiply4(mCamera);

            modelViewMatrix.translate(0, 0, -5.0f);
            modelViewMatrix.rotate(angle, 0, 1, 0);

            reflectionShader.use();

            reflectionShader.setUniformMatrix4("mvpMatrix", transformPipeline.getModelViewProjectionMatrix()); 
            reflectionShader.setUniformMatrix4("mvMatrix",  transformPipeline.getModelViewMatrix());
            reflectionShader.setUniformMatrix3("normalMatrix",transformPipeline.getNormalMatrix());
            reflectionShader.setUniformMatrix4("mInverseCamera",  mCameraInverse);
            reflectionShader.setUniform1i("cubeMap", 0);
             reflectionShader.setUniform1i("tarnishMap", 1);
            
            
            glEnable(GL_CULL_FACE);
            sphere.draw(reflectionShader.getAttributeLocations());     
            glDisable(GL_CULL_FACE);
        modelViewMatrix.pop();
        
        modelViewMatrix.push();
            modelViewMatrix.multiply4(mCameraRotation);
            skyBoxShader.use();
            skyBoxShader.setUniformMatrix4("mvpMatrix", transformPipeline.getModelViewProjectionMatrix()); 
            cube.draw(skyBoxShader.getAttributeLocations());
        modelViewMatrix.pop();
	// Flush drawing commands
	Display.update();  
    }
        
        public void pollInput()
        {
            
            float linear = 0.01f;            

            if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
                    cameraFrame.moveForward(1);
                    
            }
            if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)){
                    cameraFrame.moveForward(-1);
                   
            }
            if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)){
                    cameraFrame.rotateLocalY(linear);

            }
            if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)){
                    cameraFrame.rotateLocalY(-linear);

            }
        }
    
    
       public void create() throws LWJGLException { 
            //Display    
            Display.setDisplayModeAndFullscreen(new DisplayMode(DISPLAY_WIDTH,DISPLAY_HEIGHT)); 
            
            Display.setTitle("CubeMap");
            Display.create();
            
            //Keyboard         
            Keyboard.create();   
            //Mouse          
            Mouse.setGrabbed(false);
            Mouse.create();  
            //OpenGL      
            initGL();    
            resizeGL();  
        }  
       
        public void resizeGL() {   
            glViewport(0,0,DISPLAY_WIDTH ,DISPLAY_HEIGHT);
            frustrum.setPerspective(35f, Display.getWidth()/ Display.getHeight(),1.0f, 1000.0f);
            projectionMatrix = new MatrixStack(frustrum.getProjectionMatrix());
            // Set the transformation pipeline to use the two matrix stacks 
            transformPipeline = new GeometryTransform(modelViewMatrix, projectionMatrix);
        } 
       
            public void destroy() {  
                //Methods already check if created before destroying.  
                Mouse.destroy();   
                Keyboard.destroy();
                Display.destroy();     
            } 
        
            public static void main(String[] args) {
            MultiTexture main = null;
            try { 
                 main = new MultiTexture(); 
                main.create(); 
                main.run();
            }          
            catch(Exception ex) {  
                 
            }          
            finally { 
                if(main != null) {  
                    main.destroy();
                }         
            }      
        } 
            
            public void run() 
            {
                while (!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {              
                
                pollInput();       
                render();
       
             
                Display.sync(60);
                Display.setVSyncEnabled(true); 
                
                }
            }
                        
                            	/** 
	 * Calculate how many milliseconds have passed 
	 * since last frame.
	 * 
	 * @return milliseconds passed since last frame 
	 */
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
	 
	    return delta;
	}
	
	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
        
                public int loadCubeMap(String[] files){
            int textureID = glGenTextures();
            glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
                            
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); 
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            for(int i = 0; i < 6; i++){    
                loadTexturePNG(files[i],i);
            }
            glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
            return textureID;
            
        }
        
    private void loadTexturePNG(String filename, int pos){
        
      int[] cubeEnum = {  GL_TEXTURE_CUBE_MAP_POSITIVE_X,
                     GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
                     GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                     GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
                     GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
                     GL_TEXTURE_CUBE_MAP_NEGATIVE_Z };  
        
            InputStream in;
            int width =0;
            int height = 0;    
            ByteBuffer buffer = null;
            
            try {
                in = new FileInputStream("textures/skybox/" +filename + ".png");
                PNGDecoder decoder = new PNGDecoder(in);
                width = decoder.getWidth();
                height = decoder.getHeight();
                
                buffer = BufferUtils.createByteBuffer(4 * width * height);
                decoder.decodeFlipped(buffer, width * 4, PNGDecoder.Format.RGBA);
                buffer.flip();
                in.close();
                          } catch (FileNotFoundException ex) {
                System.err.println("Failed to find the texture file: " + filename + ".png");
   
            } catch (IOException ex) {
                System.err.println("Error reading file: " + filename + ".png");

            } 

                glTexImage2D(cubeEnum[pos], 0,GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
     
                glBindTexture(GL_TEXTURE_2D, 0);

            
    }
    
    private int loadTexture(String filename, int minFilter, int magFilter, int wrapMode){
            
            InputStream in;
            int texID = glGenTextures();
            try {
                in = new FileInputStream("textures/" + filename + ".png");
                PNGDecoder decoder = new PNGDecoder(in);
                int width = decoder.getWidth();
                int height = decoder.getHeight();
                
                ByteBuffer buffer = BufferUtils.createByteBuffer(4 * width * height);
                decoder.decode(buffer, width * 4, PNGDecoder.Format.RGBA);          
                buffer.flip();
                in.close();
                
                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
                
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
                
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
                glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

//                             
                if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 
                    minFilter == GL_LINEAR_MIPMAP_NEAREST ||
                     minFilter == GL_NEAREST_MIPMAP_LINEAR ||
                     minFilter == GL_NEAREST_MIPMAP_NEAREST){
                glGenerateMipmap(GL_TEXTURE_2D);}       
                glBindTexture(GL_TEXTURE_2D, 0);
                
            } catch (FileNotFoundException e) {
                System.err.println("Failed to find the texture file: " + filename + ".png");
                
            } catch (IOException ex) {
                System.err.println("Error reading file: " + filename + ".png");
                
            } 
           
            return texID;
        }
        
}
